/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package de.sonnemann_it.helden_software.heldenbogen.originalpdf;

import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.AcroFields.Item;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.namespace.QName;
import net.sf.saxon.xpath.XPathFactoryImpl;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.ini4j.Ini;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author benjamin
 */
public class HeroPrinter {
    
    final String pluginPath;
    final String mappingFile;
    
    String currentKey;
    String currentMapping;
    
    String outputPath;
    Document heroDoc;
        
    private SortedSet<String> keys;
    private XPathFactory xpathFactory;
    private XPath xpath;
    PdfReader pdfReader;
    PdfStamper pdfStamper;
    AcroFields acroFields;
    Ini properties;
    

    public XPath getXPath() {
        if (xpath == null) {
            xpath = getXpathFactory().newXPath();
        }
        return xpath;
    }

    public XPathFactory getXpathFactory() {
        if (xpathFactory == null) {            
            xpathFactory = new XPathFactoryImpl();                      
        }
        return xpathFactory;
    }

    public HeroPrinter(String pluginPath, String mappingName) {
        this.pluginPath = pluginPath;
        this.outputPath = pluginPath;
        this.mappingFile = pluginPath +System.getProperty("file.separator") + "mappings" + System.getProperty("file.separator") + mappingName + ".ini";
        
        try{
            properties = new Ini(new FileInputStream(this.mappingFile));            
        }catch (Exception ex) {
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    Object doXPath(String query, QName qname){
        try {
            
            XPathExpression expr = getXPath().compile(query);
            Object o = expr.evaluate(heroDoc, qname);
            return o;
        } catch (XPathExpressionException ex) {
            //Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        } catch (NullPointerException ex){
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
            System.exit(-1);
        }
        return null;
    }

    NodeList xqueryForNodeList(String query) {
        return (NodeList) doXPath(query, XPathConstants.NODESET);
    }

    Boolean xqueryForBoolean(String query){              
        return (Boolean) doXPath(query, XPathConstants.BOOLEAN);
    }
    String xqueryForString(String query){              
        return (String) doXPath(query, XPathConstants.STRING);
    }
    Integer xqueryForNumber(String query){
        return (Integer) doXPath(query, XPathConstants.NUMBER);
    }
    
    List<String> extractXPathQueries(){
        ArrayList<String> xpathQueries = new ArrayList<String>();
        int startPos  = currentMapping.indexOf("#");
        int endPos  = currentMapping.indexOf("#", startPos + 1);
        while(startPos != -1 && endPos != -1){
            String xQuery = currentMapping.substring(startPos + 1, endPos);
            currentMapping = currentMapping.substring(0, startPos) + "%s" + currentMapping.substring(endPos + 1);                                   
            xpathQueries.add(xQuery);            
            startPos  = currentMapping.indexOf("#");
            endPos  = currentMapping.indexOf("#", startPos + 1);
        }
        
        return xpathQueries;
    }
    
    void fillMainPage() {
        keys = new TreeSet<String>(acroFields.getFields().keySet());
        
        try {

            Ini.Section mappings = properties.get("mappings");
            
            for(String key : mappings.keySet()){
                String value = mappings.get(key);
                if(value == null){                                            
                    continue;
                }
                
                
                currentMapping = value;
                currentKey = key;
                if(isSimpleMapping()){
                    simpleMap();
                }else{
                    complexMap();
                }

            }

            if(properties.get("general", "debug") != null){
                for(String key : keys){
                    System.out.println(key);
                }
            }
                        
        } catch (Exception ex) {
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    boolean fillForm(Document HeroDoc, String OutputPath) {
        outputPath = OutputPath;
        heroDoc = HeroDoc;
        openPdf();
        fillMainPage();
        closePdf();
        return true;

    }
    
    private void checkNeededFile() {
        try {                        
            String filePath = String.format("%s%s%s", pluginPath, System.getProperty("file.separator"), properties.get("general", "sourcefile"));
            File f = new File(filePath);
            if(!f.exists()){
                URL url = new URL(properties.get("general", "sourceurl"));
                InputStream inStream = url.openStream();
                byte[] bytes = new byte[4096];
                FileOutputStream fout = new FileOutputStream(f);
                int read = inStream.read(bytes);
                while(read != -1){
                    fout.write(bytes, 0, read);                
                    read = inStream.read(bytes);
                }
                fout.close();                        
            }
        }catch(Exception ex){
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }    
        
    
    private void openPdf() {
        checkNeededFile();
        String name = xqueryForString("//daten/angaben/name/text()").replaceAll(" ", "-").replaceAll(",", "-");
        String profession = xqueryForString("//daten/angaben/profession/textkurz/text()").replaceAll(" ", "-").replaceAll(",", "-");
        String outFile = String.format(
                "%s%s%s-%s.pdf",
                outputPath, System.getProperty("file.separator"),
                name, profession);
        
        String pdfPath = String.format(
            "%s%s%s", 
            pluginPath, System.getProperty("file.separator"), 
            properties.get("general", "sourcefile")
        );
        try {
            pdfReader = new PdfReader(pdfPath);
            pdfStamper = new PdfStamper(pdfReader, new FileOutputStream(outFile));
            acroFields = pdfStamper.getAcroFields();
        } catch (Exception ex) {
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
        }


    }

    private void closePdf() {
        try {
            pdfStamper.setFormFlattening(true);
            pdfStamper.close();
            pdfReader.close();
            
        } catch (Exception ex) {
            Logger.getLogger(HeroPrinter.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private boolean isSimpleMapping() {
        boolean b = currentKey.matches(".*\\?s\\d{1,3}\\?.*");
        return !b;
    }

    private void simpleMap() throws Exception {
        keys.remove(currentKey);
        List<String> queries = extractXPathQueries();
        Object[] values = new Object[queries.size()];                

        switch(acroFields.getFieldType(currentKey)){
            case AcroFields.FIELD_TYPE_CHECKBOX:              

                Boolean check = xqueryForBoolean(queries.get(0));
                if(check != null){
                    acroFields.setField(currentKey,  check ? "Yes" : "No");
                    return;
                }                                                
        }

        for(int i = 0; i < queries.size(); i++){
            NodeList nl  = xqueryForNodeList(queries.get(i));                             
            if(nl != null){
                if(nl.getLength() == 1){
                    values[i] = nl.item(0).getNodeValue();
                }else if(nl.getLength() == 0){
                    values[i] = "";
                }else{
                    StringBuilder sb = new StringBuilder();
                    for(int j = 0; j < nl.getLength(); j++){
                        final Node node = nl.item(j);
                        String value = node.getNodeValue();
                        sb.append(value).append(", ");
                    }
                    values[i] = sb.toString();
                }
                continue;
            }
            
            String str = xqueryForString(queries.get(i));
            if(str != null){
                values[i] = str;
                continue;
            }
        }

        String res = String.format(currentMapping, values);
        acroFields.setField(currentKey, res);        
    }

    private void complexMap()  throws Exception{
        Pattern p = Pattern.compile(".*(\\?s\\d{1,3}\\?).*");
        Matcher m = p.matcher(currentKey);
        m.find();       
        String group = m.group(1);
        int start = Integer.parseInt(group.substring(2, group.length() - 1));
        int pos = start;
        List<String> queries = extractXPathQueries();
        Object[] values = new Object[queries.size()]; 
        
        for (int i = 0 ; i < queries.size(); i++){
            queries.set(i, queries.get(i).replace("?", "%d"));
        }
        String formatingKey = currentKey.replace(group, "%d");
        currentKey = String.format(formatingKey, pos);
        final Map<String, Item> fields = acroFields.getFields();
        
        while(fields.containsKey(currentKey) ){
            for(int i = 0; i < queries.size(); i++){
                final String query = String.format(queries.get(i), pos - start + 1);
                NodeList nl  = xqueryForNodeList(query);                             
                if(nl != null){
                    if(nl.getLength() == 1){
                        values[i] = nl.item(0).getNodeValue();
                    }else if(nl.getLength() == 0){
                        values[i] = "";
                    }else{
                        StringBuilder sb = new StringBuilder();
                        for(int j = 0; j < nl.getLength(); j++){
                            sb.append(nl.item(j).getNodeValue()).append(", ");
                        }
                        values[i] = sb.toString();
                    }
                    continue;
                }
            }
            String res = String.format(currentMapping, values);
            acroFields.setField(currentKey, res);       
            keys.remove(currentKey);
            currentKey = String.format(formatingKey, ++pos);
        }                                                      
                
    }


    
    
}
